/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.security.common;

import javax.security.auth.Subject;
import java.io.Serializable;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

/**
 * <code>Role</code>s are specially formed <code>Principal</code>s used to group together users for the purpose
 * of assigning permissions.
 * </p>
 * The <code>Principal</code> contract requires that a <code>Role</code> be uniquely identified by name. We extend
 * this contract to support the notion of parameterized roles - roles that have one or more parameters. For example,
 * a <code>Role</code> of manager might have a region parameter such as 'east coast' or 'west coast'. The parameters,
 * if present are used to dynamically parameterize the security policy. For example, a <code>PagePermission</code>
 * with name com.foo.${region}.- assigned to a manager <code>Role</code> would be interpreted as com.foo.east.- in the
 * context of a <code>Role</code> with a region parameter of east. The parameters do not affect equality or the
 * <code>Role</code>.
 */
public final class Role implements Principal, Serializable
{
    private static final long                   serialVersionUID    = 7930732926638008763L;
    private static final Class<Role>            THIS_CLASS          = Role.class;
    private static final Role[]                 NO_ROLES            = new Role[0];
    private static final Map<String,String[]>   NO_PARAMETERS       = new HashMap<String,String[]>();

    /** Role name */
    private final String                name;
    /** name (String) to value (String or String[]) */
    private final Map<String,String[]>  parameters;

    /**
     * Create a <code>Role</code> with the given name.
     * @param name the name of the <code>Role</code>.
     *      Must not be <code>null</code>.
     * @throws java.lang.IllegalArgumentException if <code>name == null</code>.
     */
    public                  Role( final String name )
    {
        this( name, NO_PARAMETERS );
    }

    /**
     * Create a <code>Role</code> with the given name and parameters.
     * @param name the name of the <code>Role</code>.
     *      Must not be <code>null</code>.
     * @param parameters The parameters associated with this <code>Role</code>. Must not be <code>null</code>.
     * @throws java.lang.IllegalArgumentException if <code>name == null</code>.
     */
    public                  Role( final String name, final Map<String,String[]> parameters )
    {
        if( name == null )
            throw new IllegalArgumentException( "name == null" );
        if( parameters == null )
            throw new IllegalArgumentException( "parameters == null" );

        this.name       = name;
        this.parameters = parameters;
    }

    /**
     * Return the paramters associated with this <code>Role</code>. Will not be <code>null</code>, but
     * may be empty. The map must not be modified.
     * @return The paramters associated with this <code>Role</code>. Will not be <code>null</code>, but
     * may be empty.
     */
    public Map<String,String[]> getParameters()
    {
        return parameters;
    }

    /**
     * Returns the name of this <code>Role</code>.
     * @return the name of this <code>Role</code>.
     */
    public String               getName()
    {
        return name;
    }

    /**
     * Returns the hash code based on role name.
     * @return the hash code.
     */
    public int                  hashCode()
    {
        return name.hashCode();
    }

    /**
     * Equality based on role name.
     * @param o Object against which to compare
     * @return <code>true</code> iff <code>o</code> is a <code>Role</code>
     *      with the same name.
     */
    public boolean              equals( final Object o )
    {
        try
        {
            return ((Role)o).name.equals( name );
        }
        catch( Exception e )
        {
            return false;
        }
    }

    /**
     * Convenience method to return the <code>Role</code>s in a
     * <code>Subject</code>.
     *
     * @param subject The <code>Subject</code> from which to extract the
     *      <code>Role</code>s.  Must not be <code>null</code>.
     *
     * @throws java.lang.IllegalArgumentException if <code>subject == null</code>.
     *
     * @return The <code>Roles</code>s of the <code>Subject</code>.
     *      Guaranteed to not return <code>null</code> though may return
     *      an empty array.
     */
    public static Role[]        getRoles( final Subject subject )
    {
        if( subject == null )
            throw new IllegalArgumentException( "subject == null" );

        return subject.getPrincipals( THIS_CLASS ).toArray( NO_ROLES );
    }

    /**
     * Convenience method to return the <code>Role</code>s of the
     * <code>Subject</code> associated with the current thread.
     *
     * @throws java.lang.IllegalArgumentException if there is no <code>Subject</code>
     *      associated with the current thread.
     *
     * @return The <code>Roles</code>s of the current <code>Subject</code>.
     *      Guaranteed to not return <code>null</code> though may return
     *      an empty array.
     */
    public static Role[]        getRoles()
    {
        return getRoles( ContextSubject.getContextSubject() );
    }

    /**
     * Convenience method to test if the <code>Subject</code> associated with
     * the current thread includes the given <code>Role</code>. If there is no
     * <code>Subject</code> associated with the current thread, this method
     * returns <code>false</code>.
     *
     * @param role the <code>Role</code>.  Must not be <code>null</code>.
     *
     * @throws java.lang.IllegalArgumentException if <code>role == null</code>.
     *
     * @return <code>true</code> iff the current thread has an associated
     *      <code>Subject</code> and the <code>Subject</code> contains the
     *      given <code>Role</code>.
     */
    public static boolean       isCallerInRole( final Role role )
    {
        if( role == null )
            throw new IllegalArgumentException( "role == null" );

        Subject subject = ContextSubject.getContextSubject();

        return subject != null && subject.getPrincipals().contains( role );
    }
}


